// ! 渲染内核 必须导入
import "@kitware/vtk.js/Rendering/Profiles/Geometry"
import "@kitware/vtk.js/Rendering/Profiles/Glyph"
import vtkOpenGLRenderWindow from "@kitware/vtk.js/Rendering/OpenGL/RenderWindow"
import vtkRenderWindow from "@kitware/vtk.js/Rendering/Core/RenderWindow"
import vtkRenderer from "@kitware/vtk.js/Rendering/Core/Renderer"
// ! 重要
import vtkInteractorObserver from "@kitware/vtk.js/Rendering/Core/InteractorObserver.js"
import vtkInteractorStyleTrackballCamera from "@kitware/vtk.js/Interaction/Style/InteractorStyleTrackballCamera"
import vtkRenderWindowInteractor from "@kitware/vtk.js/Rendering/Core/RenderWindowInteractor"
//@ts-ignore
const { computeWorldToDisplay } = vtkInteractorObserver
// ? ------- 华丽的分割线 --------;
import { VTActor, BActor } from "./Core"
import { devtool } from "./devtool"
import { Selector } from "./Selector"
import { throttle, Vector3 } from "stl-typescript"
import { LabelActor, SceneType } from "./index"
import { debounce } from "stl-typescript"

// * 颜色映射相关
// eslint-disable-next-line @typescript-eslint/ban-ts-comment

import { ArrayPlus } from "./Core/ArrayPlus"
import { ACTOR_TYPE, IMouseEvent, IKeyEvent } from "./interface"
import { NormalSystem } from "./System/NormalSystem"
import { WidgetSystem } from "./System/WidgetSystem"

import {
  openPerformanceMonitor,
  system_clear,
} from "./App/index"
import { initContainer } from "./App/index"
import { HookType } from "./interface"
import vtkPixelSpaceCallbackMapper from "@kitware/vtk.js/Rendering/Core/PixelSpaceCallbackMapper"
import vtkActor from "@kitware/vtk.js/Rendering/Core/Actor"
import vtkPolyData from "@kitware/vtk.js/Common/DataModel/PolyData"

import vtk from "@kitware/vtk.js/vtk"
// Need polydata registered in the vtk factory
import "@kitware/vtk.js/Common/Core/Points"
import "@kitware/vtk.js/Common/Core/DataArray"
import "@kitware/vtk.js/Common/Core/StringArray"
import "@kitware/vtk.js/Common/DataModel/PolyData"
import vtkMapper from "@kitware/vtk.js/Rendering/Core/Mapper"
import { FieldAssociations } from "@kitware/vtk.js/Common/DataModel/DataSet/Constants"
import { ReportScene } from "./Scene/ReportScene"
import { AxisSystem } from "./System/AxisSystem.ts"
import vtkInteractorStyle from "@kitware/vtk.js/Rendering/Core/InteractorStyle"

export enum VRenderMode {
  HIDDEN = "Hidden",
  POINTS = "Points",
  WIREFRAME = "Wireframe",
  SURFACE = "Surface",
  SURFACE_WITH_EDGE = "SurfaceWithEdge",
}

export interface ApplicationOptions {
  // * debug模式
  debug?: boolean
  enableAxisHelper?: boolean
}

// 定义一个函数，用于将一个值从一个范围映射到另一个范围
function affine(val, inMin, inMax, outMin, outMax) {
  // 计算输入值在输入范围内的比例
  return ((val - inMin) / (inMax - inMin)) * (outMax - outMin) + outMin
}

// * 深度测试工具
function initializeDebugHandler(renderWindow) {
  const debugCanvas = document.createElement("canvas")
  const debugCtx = debugCanvas.getContext("2d")

  const debugCanvasSize = 1 / 4
  let dbgWidth = 0
  let dbgHeight = 0
  let lastDepthBuffer = null
  debugCanvas.style.position = "absolute"
  debugCanvas.style.bottom = "25px"
  debugCanvas.style.right = "25px"
  debugCanvas.style.zIndex = "99999"
  document.body.appendChild(debugCanvas)
  // debugCanvas.addEventListener('click', (evt) => {
  //   evt.preventDefault();
  //   evt.stopPropagation();
  //   const x = (evt.pageX - debugCanvas.offsetLeft) / debugCanvasSize;
  //   const y = (evt.pageY - debugCanvas.offsetTop) / debugCanvasSize;

  //   if (lastDepthBuffer && dbgWidth > 0 && dbgHeight > 0) {
  //     const dIdx =
  //       ((dbgHeight - 1 - Math.floor(y)) * dbgWidth + Math.floor(x)) * 4;
  //     const r = lastDepthBuffer[dIdx] / 255;
  //     const g = lastDepthBuffer[dIdx + 1] / 255;
  //     let z = (r * 256 + g) / 257;
  //     z = z * 2 - 1; // scale depths from [0, 1] into [-1, 1]
  //     console.log(`depth at (${x}, ${y}) is ${z}`);

  //     const activeCamera = renderWindow.getRenderers()[0].getActiveCamera();
  //     const crange = activeCamera.getClippingRange();
  //     // console.log(`current clipping range: [${crange[0]}, ${crange[1]}]`);
  //   } else {
  //     console.log(`click(${x}, ${y})`);
  //   }
  // });

  return {
    update: (coordsList, depthBuffer) => {
      debugCtx.fillStyle = "rgba(255, 255, 255, 1.0)"
      debugCtx.clearRect(0, 0, dbgWidth, dbgHeight)

      if (!depthBuffer) {
        console.error("Expected a depthBuffer!")
        return
      }

      lastDepthBuffer = depthBuffer

      if (dbgWidth === 0 || dbgHeight === 0) {
        console.log("No size yet, cannot draw debug canvas")
        return
      }

      const depthRange = [10000000, -10000000]

      const imageData = debugCtx.getImageData(0, 0, dbgWidth, dbgHeight)
      const data = imageData.data
      for (let y = 0; y < imageData.height; y += 1) {
        for (let x = 0; x < imageData.width; x += 1) {
          // Going back to the raw pixels again here, so they need to be
          // flipped in y as the Core/PixelSpaceCallbackMapper already did.
          const dIdx = ((imageData.height - 1 - y) * imageData.width + x) * 4

          const r = depthBuffer[dIdx] / 255
          const g = depthBuffer[dIdx + 1] / 255
          const z = (r * 256 + g) / 257
          // z = (z * 2) - 1;   // scale depths from [0, 1] into [-1, 1]
          const zColor = affine(z, 0, 1, 0, 255)
          const pIdx = (y * imageData.width + x) * 4
          data[pIdx] = zColor
          data[pIdx + 1] = zColor
          data[pIdx + 2] = zColor
          data[pIdx + 3] = 255

          if (z < depthRange[0]) {
            depthRange[0] = z
          }

          if (z > depthRange[1]) {
            depthRange[1] = z
          }

          // z = (z * 2) - 1;   // scale depths from [0, 1] into [-1, 1]

          // const pdepth = depthValues[dIdx] * 255;
          // const pdepth = affine(depthValues[dIdx], depthRange.min, depthRange.max, 0, 255);
          // const pIdx = ((y * imageData.width) + x) * 4;
          // data[pIdx] = pdepth;
          // data[pIdx + 1] = pdepth;
          // data[pIdx + 2] = pdepth;
          // data[pIdx + 3] = 255;
        }
      }
      debugCtx.putImageData(imageData, 0, 0)

      // console.log(`depth range: [${depthRange[0]}, ${depthRange[1]}], delta: ${depthRange[1] - depthRange[0]}`);
    },
    resize: (w, h) => {
      const aspect = w / h
      const sw = w * debugCanvasSize
      const sh = sw / aspect
      debugCanvas.setAttribute("width", w)
      debugCanvas.setAttribute("height", h)
      debugCanvas.style.width = `${sw}px`
      debugCanvas.style.height = `${sh}px`
      dbgWidth = w
      dbgHeight = h
    },
  }
}

export class Application {
  private parent: any
  container: HTMLElement
  inner_container: HTMLElement
  instance: vtkOpenGLRenderWindow
  window: vtkRenderWindow
  renderer: vtkRenderer
  selector: Selector = new Selector(this)
  hardWareSelector: any
  // scaleBar: vtkScalarBarActor;
  actorList: ArrayPlus<VTActor> = new ArrayPlus([])
  // 已经被隐藏的partId以及对应的actor
  entityId2gyActor: Map<number, any> = new Map()
  public actorMaskedMap: Map<number, any> = new Map()
  public graphicActorList: ArrayPlus<VTActor> = new ArrayPlus([])
  public labelActorList: ArrayPlus<LabelActor> = new ArrayPlus([])
  private afterRenderCallBackList: ArrayPlus<() => void> = new ArrayPlus([])

  private d_render: any

  // * 当前选中表面ID
  currentSelectFaceId = -1
  // * 当前选中网格ID
  currentSelectMeshId = -1
  // * 当前选中网格点ID
  currentSelectMeshPointId = -1

  private _renderMode: VRenderMode = VRenderMode.SURFACE
  private firstEnter = true

  private mouseMoveListenerList = new ArrayPlus<any>([])
  private mouseLeftButtonPressList = new ArrayPlus<any>([])
  private mouseRightButtonPressList = new ArrayPlus<any>([])
  private mouseMoveListenerNoLimitList = new ArrayPlus<any>([])
  private mouseWheelListenerList = new ArrayPlus<any>([])
  // keyDown
  private keyDownListener = new ArrayPlus<any>([])

  private MOUSE_MOVE_FREQ = 100
  private KEY_DOWN_FREQ = 100

  private lastRenderTime = 0

  private SystemNormal = new NormalSystem(this)
  private SystemWidget = new WidgetSystem(this)
  // * 辅助坐标系统
  private SystemAxis

  public zHelper = vtkActor.newInstance()
  public enableZ = false
  public lastZBuffer = undefined

  reportScene: ReportScene | null = null
  // * 交互器
  private _interactor
  private standardDistance = 20
  private scaleFactor = 1

  // @ api
  api = {
    system: {
      clear: () => {
        system_clear(this)
      },
    },
    file: {
      load: () => {
        // TODO
        console.log("todo")
      },
    },
    scene: {
      create: (type: string) => {
        switch (type) {
          case SceneType.ReportScene:
            this.reportScene = new ReportScene(this)
            break

          default:
            break
        }
      },
    },
    camera: {
      get: () => {
        return this.camera
      },
    },
  }

  constructor(
    container: HTMLElement,
    parent: any,
    opt: ApplicationOptions = {}
  ) {
    this.parent = parent
    // #start 初始化容器 block-001
    const { inner_container } = initContainer(this, container)
    this.container = container
    this.inner_container = inner_container
    // #end block-001
    this.instance = vtkOpenGLRenderWindow.newInstance()
    this.instance.setContainer(inner_container)
    this.instance.setSize(
      inner_container.clientWidth,
      inner_container.clientHeight
    )
    this.renderer = vtkRenderer.newInstance({
      background: [237 / 255, 239 / 255, 242 / 255],
    })
    this.window = vtkRenderWindow.newInstance()
    this.renderer.getActiveCamera().setClippingRange(0.1, 10000)
    this.window.addView(this.instance)
    this.window.addRenderer(this.renderer)

    // * 🍔💶 设置交互器
    const interactor = vtkRenderWindowInteractor.newInstance()
    interactor.setView(this.instance)
    interactor.initialize()
    interactor.setContainer(inner_container)
    interactor.setInteractorStyle(
      vtkInteractorStyleTrackballCamera.newInstance()
    )
    this._interactor = interactor

    // * 设置硬件选择器
    const hardPicker = interactor.getView().getSelector()
    hardPicker.setCaptureZValues(true)
    hardPicker.setFieldAssociation(FieldAssociations.FIELD_ASSOCIATION_CELLS)
    this.hardWareSelector = hardPicker

    // * 初始化opengl选择器
    this.selector.initOpenGLPicker()

    this.selector.initBoxSelector()
    // !! 暂时不用
    // this.initZHelper();
    // !! 初始化小坐标轴 - 默认需要初始化
    if (opt.enableAxisHelper || opt.enableAxisHelper === undefined) {
      this.SystemAxis = new AxisSystem(this)
    }
    // !! close shadow
    this.renderer.setUseShadows(false)
    this.window.render()
    this.renderer.onEvent((ev) => {
      this.update()
    })

    this.registerListener()
    // # 合并渲染
    this.d_render = debounce(
      () => {
        const renderStart = performance.now()
        this.window.render()
        this.lastRenderTime = performance.now()
        const renderEnd = performance.now()
        devtool.success("引擎渲染耗时", renderEnd - renderStart, "ms")
        this.afterRenderCallBackList.foreach((callback) => {
          callback()
        })
      },
      16,
      false
    )
    if (opt.debug) {
      openPerformanceMonitor(this)
    }
    this.SystemAxis && this.SystemAxis.mount()
  }

  private registerListener() {
    this.window.getInteractor().onRenderEvent(() => {
      if (this.SystemAxis) {
        // 同步更新小坐标轴
        this.SystemAxis.update(this.renderer.getActiveCamera())
      }
    })

    this.window.getInteractor().onLeftButtonPress((event: IMouseEvent) => {
      this.mouseLeftButtonPressList.foreach((callback) => {
        callback(event)
      })
    })

    this.window.getInteractor().onRightButtonPress((event: IMouseEvent) => {
      this.mouseRightButtonPressList.foreach((callback) => {
        callback(event)
      })
    })

    this.window.getInteractor().onMouseWheel((event: IMouseEvent) => {
      const currentDistance = this.camera.getDistance()      
      this.scaleFactor = Number((this.standardDistance / currentDistance).toFixed(2))
    })

    const keydown = throttle((event: any) => {
      if (this.keyDownListener.value.length > 0) {
        this.keyDownListener.foreach((callback) => {
          callback(event)
        })
      }
    }, this.KEY_DOWN_FREQ)
    this.window.getInteractor().onKeyDown((event: any) => {
      keydown(event)
    })

    const mouseMove = throttle((event: IMouseEvent) => {
      if (this.mouseMoveListenerList.value.length > 0) {
        this.mouseMoveListenerList.foreach((callback) => {
          callback(event)
        })
      }
    }, this.MOUSE_MOVE_FREQ)

    this.window.getInteractor().onMouseMove((event: IMouseEvent) => {
      mouseMove(event)
      this.mouseMoveListenerNoLimitList.foreach((callback) => {
        callback(event)
      })
    })

    const resize = () => {
      this.instance.setSize(
        this.container.clientWidth,
        this.container.clientHeight
      )
      this.renderAtOnce()
    }
    window.removeEventListener("resize", resize)
    window.addEventListener("resize", resize)
  }

  registerMouseMoveListener(callback: (event: IMouseEvent) => void) {
    this.mouseMoveListenerList.push(callback)
  }
  unregisterMouseMoveListener(callback: (event: IMouseEvent) => void) {
    this.mouseMoveListenerList.remove(callback)
  }
  clearMouseMoveButtonPress() {
    this.mouseMoveListenerList.empty()
  }

  registerMouseMoveListenerNoLimit(callback: (event: IMouseEvent) => void) {
    this.mouseMoveListenerNoLimitList.push(callback)
  }

  registerMouseRightListener(callback: (event: IMouseEvent) => void) {
    this.mouseRightButtonPressList.push(callback)
  }

  unregisterMouseRightListener(callback: (event: IMouseEvent) => void) {
    this.mouseRightButtonPressList.remove(callback)
  }
  clearMouseRightButtonPress() {
    this.mouseRightButtonPressList.empty()
  }

  registerKeyDownListener(callback: (event: IKeyEvent) => void) {
    this.keyDownListener.push(callback)
  }
  unregisterKeyDownListener(callback: (event: IKeyEvent) => void) {
    this.keyDownListener.remove(callback)
  }
  hasMouseDownEvent(callback: (event: IMouseEvent) => void) {
    return (
      this.mouseLeftButtonPressList.someEvent(callback) ||
      this.mouseRightButtonPressList.someEvent(callback)
    )
  }
  hasMouseMoveEvent(callback: (event: IMouseEvent) => void) {
    return (
      this.mouseMoveListenerList.someEvent(callback) ||
      this.mouseMoveListenerNoLimitList.someEvent(callback)
    )
  }

  hasKeyDownEvent(callback: (event: IKeyEvent) => void) {
    return this.keyDownListener.someEvent(callback)
  }

  // * 一段时间内的会合并渲染
  render() {
    this.d_render()
  }

  // * 立即渲染
  renderAtOnce() {
    this.window.render()
  }

  // 添加actor时，在map中记录partId和对应的GyActor。
  addEntityId2gyActor(actor: any) {
    if (actor.isGyActor === true) {
      this.entityId2gyActor.set(actor.partId, actor)
    }
  }

  // 移除actor时，在map中删除partId和对应的GyActor记录。
  removeEntityId2gyActor(actor: any) {
    if (actor.isGyActor === true) {
      this.entityId2gyActor.delete(actor.partId)
    }
  }

  add(actor: BActor) {
    this.addEntityId2gyActor(actor)
    // 这一步添加了主场景
    if (actor.type === ACTOR_TYPE.VT_ACTOR) {
      const t = actor as VTActor
      this.selector.addToTargetList(t)
      this.selector.setPointTargetList(t)
      if (t.outlines) {
        this.renderer.addActor(t.outlines.instance)
      }
      if (t.vertexAc) {
        t.vertexAc.visible = false
        this.renderer.addActor(t.vertexAc.instance)
      }
      this.renderer.addActor(actor.instance)
      this.actorList.push(t)
      this.updateCamera()
    } else if (actor.type === ACTOR_TYPE.GRAPHIC_ACTOR) {
      this.renderer.addActor(actor.instance)
      this.updateCamera()
      this.graphicActorList.push(actor as VTActor)
    } else if (actor.type === ACTOR_TYPE.LABEL) {
      this.labelActorList.push(actor as LabelActor)
      this.renderer.addActor((actor as LabelActor).flag.instance)
    } else if (actor.type === ACTOR_TYPE.WIDGET) {
      // TODO 这里需要优化
      ;(actor as any).children.forEach((actor: vtkActor) => {
        this.renderer.addActor(actor)
      })
    }
    this.render()
    if (actor.type === ACTOR_TYPE.LABEL) {
      ;(actor as LabelActor).mount(this.renderer, this.container.clientHeight)
      this.container.append(actor.instance)
    }
  }

  remove(actor: BActor) {
    this.removeEntityId2gyActor(actor)
    if (actor.type === ACTOR_TYPE.VT_ACTOR) {
      const t = actor as VTActor
      this.selector.removeFromTargetList(t)
      this.selector.removePointTargetList(t)
      this.renderer.removeActor(actor.instance)
      if (t.outlines) {
        this.renderer.removeActor(t.outlines.instance)
      }
      if (t.vertexAc) {
        t.vertexAc.visible = false
        this.renderer.removeActor(t.vertexAc.instance)
      }
      this.actorList.remove(actor as VTActor)
    } else if (actor.type === ACTOR_TYPE.GRAPHIC_ACTOR) {
      this.renderer.removeActor(actor.instance)
      this.updateCamera()
      this.graphicActorList.remove(actor as VTActor)
    } else if (actor.type === ACTOR_TYPE.LABEL) {
      const ac = actor as LabelActor
      ac.instance.remove()
      this.labelActorList.remove(ac)
    }
    this.render()
  }

  clear() {
    this.actorList.foreach((actor) => {
      this.remove(actor)
    })
    this.graphicActorList.foreach((actor) => {
      this.remove(actor)
    })
    this.labelActorList.foreach((actor) => {
      this.remove(actor)
    })
  }

  // 根据partId对actor进行遮罩 
  // * 根据partId把actor移出场景
  mask(partId: number) {
    // 遍历actorList
    this.actorList.foreach((actor) => {
      // 判断actor是否为GyActor
      if ((actor as any).isGyActor === true) {
        // 判断actor的partId是否与传入的partId相等
        if ((actor as any).partId == partId) {
          // 移除actor
          this.remove(actor)
          // 将actor添加到actorMaskedMap中
          this.actorMaskedMap.set(partId, actor)
          // 返回
          return
        }
      }
    })
  }

  // 解除遮罩
  // * 根据partId把actor塞回场景
  unmask(partId: number) {
    // 获取遮罩的演员
    const actor = this.actorMaskedMap.get(partId)
    // 如果演员存在
    if (actor) {
      // 将演员添加到场景中
      this.add(actor)
      // 从遮罩地图中删除该演员
      this.actorMaskedMap.delete(partId)
    }
  }

  get renderMode() {
    return this._renderMode
  }

  set renderMode(v: VRenderMode) {
    this._renderMode = v
  }

  update() {
    this.updateLabelActors()
    this.SystemNormal.update()
  }

  updateLabelActors() {
    this.labelActorList.foreach((item) => {
      item.update(this.scaleFactor)
    })
  }

  // 设置entity的颜色
  set_entity_color(entity_id: number, rgb: number[]) {
    this.entityId2gyActor.get(entity_id)?.setDefaultFaceColor(rgb)
    this.render()
  }

  onHooks(type: HookType, callback: () => void) {
    switch (type) {
      case HookType.AFTER_RENDER:
        this.afterRenderCallBackList.push(callback)
        break
    }
  }

  updateCamera() {
    if (this.firstEnter) {
      this.renderer.resetCamera()
      this.firstEnter = false
      this.standardDistance = this.camera.getDistance()
      this.scaleFactor = 1
    }
  }

  getSystemNormal() {
    return this.SystemNormal
  }

  getSystemWidget() {
    return this.SystemWidget
  }

  get camera() {
    return this.renderer.getActiveCamera()
  }

  /**
   * @description 尝试根据单元法向量把相机朝向屏幕
   * @param normal 法向量
   */
  tryFaceToCell(normal: Vector3) {
    const cameraVec = new Vector3(...this.camera.getPosition())
      .sub(new Vector3(...this.camera.getFocalPoint()))
      .normalize()

    // * xz 平面投影向量
    const xz1 = normal.clone().projectOnPlane(new Vector3(0, 1, 0))
    const xz2 = cameraVec.clone().projectOnPlane(new Vector3(0, 1, 0))

    if (
      (xz1.x === 0 && xz1.z === 0 && xz1.y === 0) ||
      (xz2.x === 0 && xz2.z === 0 && xz2.y === 0)
    ) {
    } else {
      const angle = (xz1.angleTo(xz2) / Math.PI) * 180
      this.camera.azimuth(angle)
    }
    // * yz 平面投影向量
    const yz1 = normal.clone().projectOnPlane(new Vector3(1, 0, 0))
    const yz2 = cameraVec.clone().projectOnPlane(new Vector3(1, 0, 0))
    if (
      (yz1.x === 0 && yz1.y === 0 && yz1.z === 0) ||
      (yz2.x === 0 && yz2.y === 0 && yz2.z === 0)
    ) {
    } else {
      const angle2 = (yz1.angleTo(yz2) / Math.PI) * 180
      this.camera.elevation(angle2)
    }

    this.renderAtOnce()
  }

  initZHelper() {
    // #start  用来做深度测试的
    // const tool = initializeDebugHandler(this.window);
    // tool.resize(this.container.clientWidth, this.container.clientHeight);
    // const planePoly = vtk({
    //   vtkClass: "vtkPolyData",
    //   points: {
    //     vtkClass: "vtkPoints",
    //     dataType: "Float32Array",
    //     numberOfComponents: 3,
    //     values: [-1, -1, 0, 1, -1, 0, 1, 1, 0, -1, 1, 0],
    //   },
    //   polys: {
    //     vtkClass: "vtkCellArray",
    //     dataType: "Uint16Array",
    //     values: [3, 0, 1, 2, 3, 0, 2, 3],
    //   },
    // });
    // const planeMapper = vtkMapper.newInstance();
    // const planeActor = vtkActor.newInstance();
    // planeMapper.setInputData(planePoly);
    // planeActor.setMapper(planeMapper);
    // this.renderer.addActor(planeActor);
    // #end  用来做深度测试的
    const a = vtkPixelSpaceCallbackMapper.newInstance({
      useZValues: true,
    })
    a.setCallback((coords, camera, asp, depthBuffer) => {
      // ! 用来做深度测试的
      // tool.update(coords, depthBuffer);
      if (!this.enableZ) return
      this.lastZBuffer = depthBuffer
    })
    a.setInputData(vtkPolyData.newInstance())
    const t = vtkActor.newInstance()
    t.setMapper(a)
    this.renderer.addActor(t)
  }

  getZByPos(x: number, y: number) {
    if (!this.lastZBuffer) {
      throw new Error("no z buffer")
    }
    x = Math.floor(x)
    y = Math.floor(y)
    const dIdx =
      ((this.container.clientWidth - 1 - y) * this.container.clientHeight + x) *
      4
    const r = this.lastZBuffer[dIdx] / 255
    const g = this.lastZBuffer[dIdx + 1] / 255
    const z = (r * 256 + g) / 257
    return z
  }
}
